// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SerializationResolver.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.Runtime
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Reflection;
    using System.Runtime.Serialization;

    /// <summary>
    /// Class used during serialization to resolve the names of the types in the XML file.
    /// Since we don't know the set of serializable types at build time, 
    /// the custom behavior here is to search all loaded assemblies for types marked with DataContract attribute
    /// </summary>
    /// <remarks>
    /// This class is intended for manifest serialization only. 
    /// Serialization of recordings doesn't need this functionality, and should not use it because it lacks any type caching.
    /// </remarks>
    public class SerializationResolver : DataContractResolver
    {
        /// <summary>
        /// Returns the type for the specified name.
        /// </summary>
        /// <param name="typeName">The type name provided in the file</param>
        /// <param name="typeNamespace">The type namespace provided in the file</param>
        /// <param name="declaredType">The declared type (e.g. a base type)</param>
        /// <param name="knownTypeResolver">The default resolver.</param>
        /// <returns>The type to use</returns>
        public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
        {
            Type resultType = null;

            // search the loaded assemblies only
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                // Compiled XSLT adds a dynamic assembly (System.Xml.Xsl.CompiledQuery)
                // from which we cannot get ExportedTypes.
                if (!assembly.IsDynamic)
                {
                    // look for types annotated with DataContract
                    foreach (Type type in assembly.ExportedTypes)
                    {
                        if (type.IsGenericTypeDefinition || type.IsAbstract || type.IsInterface)
                        {
                            continue;
                        }

                        DataContractAttribute dc = type.GetCustomAttribute<DataContractAttribute>();

                        // if the type belongs to a namespace that has a ContractNamespaceAttribute mapping, remember that name
                        string globalNamespaceName = null;
                        foreach (ContractNamespaceAttribute nc in assembly.GetCustomAttributes<ContractNamespaceAttribute>())
                        {
                            if (nc.ClrNamespace == type.Namespace)
                            {
                                globalNamespaceName = nc.ContractNamespace;
                            }
                        }

                        if (dc != null)
                        {
                            string contractName = dc.Name ?? type.Name;

                            // The first namespace choice is the DataContractAttribute value, the second is the ContractNamespaceAttribute value, the last is the CLR namespace
                            string contractNamespace = dc.Namespace ?? (globalNamespaceName ?? string.Format("http://schemas.datacontract.org/2004/07/{0}", type.Namespace));
                            if (contractName == typeName && contractNamespace == typeNamespace)
                            {
                                resultType = type;
                            }
                        }
                    }
                }
            }

            return resultType;
        }

        /// <summary>
        /// Provides the names to serialize for a given type.
        /// </summary>
        /// <param name="type">The type about to be serialized</param>
        /// <param name="declaredType">The declared type (e.g. base type)</param>
        /// <param name="knownTypeResolver">The default resolver</param>
        /// <param name="typeName">The type name to use when serializing</param>
        /// <param name="typeNamespace">The namespace to use when serializing</param>
        /// <returns>True if the type could be resolved</returns>
        public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out System.Xml.XmlDictionaryString typeName, out System.Xml.XmlDictionaryString typeNamespace)
        {
            return knownTypeResolver.TryResolveType(type, declaredType, knownTypeResolver, out typeName, out typeNamespace);
        }
    }
}
